import { MANIFEST_FIELDS } from '../../constants.js';
import { isValidLicense } from './util.js';
import { normalizePerson, extractDescription } from './util.js';
import inferLicense from './infer-license.js';
import * as fs from '../fs.js';
import semver from 'semver';
import * as path from 'path';
import * as nodeUrl from 'url';
const LICENSE_RENAMES = {
    'MIT/X11': 'MIT',
    X11: 'MIT',
};
export default (async function (info, moduleLoc, reporter, warn) {
    const files = await fs.readdir(moduleLoc);
    // clean info.version
    if (typeof info.version === 'string') {
        info.version = semver.clean(info.version) || info.version;
    }
    // if name or version aren't set then set them to empty strings
    info.name = info.name || '';
    info.version = info.version || '';
    // if the man field is a string then coerce it to an array
    if (typeof info.man === 'string') {
        info.man = [info.man];
    }
    // if the keywords field is a string then split it on any whitespace
    if (typeof info.keywords === 'string') {
        info.keywords = info.keywords.split(/\s+/g);
    }
    // if there's no contributors field but an authors field then expand it
    if (!info.contributors && files.indexOf('AUTHORS') >= 0) {
        const authorsFilepath = path.join(moduleLoc, 'AUTHORS');
        const authorsFilestats = await fs.stat(authorsFilepath);
        if (authorsFilestats.isFile()) {
            let authors = await fs.readFile(authorsFilepath);
            info.contributors = authors
                .split(/\r?\n/g) // split on lines
                .map((line) => line.replace(/^\s*#.*$/, '').trim()) // remove comments
                .filter((line) => !!line); // remove empty lines;
        }
    }
    // expand people fields to objects
    if (typeof info.author === 'string' || typeof info.author === 'object') {
        info.author = normalizePerson(info.author);
    }
    if (Array.isArray(info.contributors)) {
        info.contributors = info.contributors.map(normalizePerson);
    }
    if (Array.isArray(info.maintainers)) {
        info.maintainers = info.maintainers.map(normalizePerson);
    }
    // if there's no readme field then load the README file from the cwd
    if (!info.readme) {
        const readmeCandidates = files
            .filter((filename) => {
            const lower = filename.toLowerCase();
            return lower === 'readme' || lower.indexOf('readme.') === 0;
        })
            .sort((filename1, filename2) => {
            // favor files with extensions
            return filename2.indexOf('.') - filename1.indexOf('.');
        });
        for (const readmeFilename of readmeCandidates) {
            const readmeFilepath = path.join(moduleLoc, readmeFilename);
            const readmeFileStats = await fs.stat(readmeFilepath);
            if (readmeFileStats.isFile()) {
                info.readmeFilename = readmeFilename;
                info.readme = await fs.readFile(readmeFilepath);
                break;
            }
        }
    }
    // if there's no description then take the first paragraph from the readme
    if (!info.description && info.readme) {
        const desc = extractDescription(info.readme);
        if (desc) {
            info.description = desc;
        }
    }
    // support array of engine keys
    if (Array.isArray(info.engines)) {
        const engines = {};
        for (const str of info.engines) {
            if (typeof str === 'string') {
                const [name, ...patternParts] = str.trim().split(/ +/g);
                engines[name] = patternParts.join(' ');
            }
        }
        info.engines = engines;
    }
    // allow bugs to be specified as a string, expand it to an object with a single url prop
    if (typeof info.bugs === 'string') {
        info.bugs = { url: info.bugs };
    }
    // normalize homepage url to http
    if (typeof info.homepage === 'string') {
        const parts = nodeUrl.parse(info.homepage);
        parts.protocol = parts.protocol || 'http:';
        if (parts.pathname && !parts.hostname) {
            parts.hostname = parts.pathname;
            parts.pathname = '';
        }
        info.homepage = nodeUrl.format(parts);
    }
    // if the `bin` field is as string then expand it to an object with a single property
    // based on the original `bin` field and `name field`
    // { name: "foo", bin: "cli.js" } -> { name: "foo", bin: { foo: "cli.js" } }
    if (typeof info.name === 'string' && typeof info.bin === 'string' && info.bin.length > 0) {
        // Remove scoped package name for consistency with NPM's bin field fixing behaviour
        const name = info.name.replace(/^@[^\/]+\//, '');
        info.bin = { [name]: info.bin };
    }
    // bundleDependencies is an alias for bundledDependencies
    if (info.bundledDependencies) {
        info.bundleDependencies = info.bundledDependencies;
        delete info.bundledDependencies;
    }
    let scripts;
    // dummy script object to shove file inferred scripts onto
    if (info.scripts && typeof info.scripts === 'object') {
        scripts = info.scripts;
    }
    else {
        scripts = {};
    }
    // if there's a server.js file and no start script then set it to `node server.js`
    if (!scripts.start && files.indexOf('server.js') >= 0) {
        scripts.start = 'node server';
    }
    // if there's a binding.gyp file and no install script then set it to `node-gyp rebuild`
    if (!scripts.install && files.indexOf('binding.gyp') >= 0) {
        scripts.install = 'node-gyp rebuild';
    }
    // set scripts if we've polluted the empty object
    if (Object.keys(scripts).length) {
        info.scripts = scripts;
    }
    const dirs = info.directories;
    if (dirs && typeof dirs === 'object') {
        const binDir = dirs.bin;
        if (!info.bin && binDir && typeof binDir === 'string') {
            const bin = (info.bin = {});
            const fullBinDir = path.join(moduleLoc, binDir);
            if (await fs.exists(fullBinDir)) {
                for (const scriptName of await fs.readdir(fullBinDir)) {
                    if (scriptName[0] === '.') {
                        continue;
                    }
                    bin[scriptName] = path.join('.', binDir, scriptName);
                }
            }
            else {
                warn(reporter.lang('manifestDirectoryNotFound', binDir, info.name));
            }
        }
        const manDir = dirs.man;
        if (!info.man && typeof manDir === 'string') {
            const man = (info.man = []);
            const fullManDir = path.join(moduleLoc, manDir);
            if (await fs.exists(fullManDir)) {
                for (const filename of await fs.readdir(fullManDir)) {
                    if (/^(.*?)\.[0-9]$/.test(filename)) {
                        man.push(path.join('.', manDir, filename));
                    }
                }
            }
            else {
                warn(reporter.lang('manifestDirectoryNotFound', manDir, info.name));
            }
        }
    }
    delete info.directories;
    // normalize licenses field
    const licenses = info.licenses;
    if (Array.isArray(licenses) && !info.license) {
        let licenseTypes = [];
        for (let license of licenses) {
            if (license && typeof license === 'object') {
                license = license.type;
            }
            if (typeof license === 'string') {
                licenseTypes.push(license);
            }
        }
        licenseTypes = licenseTypes.filter(isValidLicense);
        if (licenseTypes.length === 1) {
            info.license = licenseTypes[0];
        }
        else if (licenseTypes.length) {
            info.license = `(${licenseTypes.join(' OR ')})`;
        }
    }
    const license = info.license;
    // normalize license
    if (license && typeof license === 'object') {
        info.license = license.type;
    }
    // get license file
    const licenseFile = files.find((filename) => {
        const lower = filename.toLowerCase();
        return (lower === 'license' || lower.startsWith('license.') || lower === 'unlicense' || lower.startsWith('unlicense.'));
    });
    if (licenseFile) {
        const licenseFilepath = path.join(moduleLoc, licenseFile);
        const licenseFileStats = await fs.stat(licenseFilepath);
        if (licenseFileStats.isFile()) {
            const licenseContent = await fs.readFile(licenseFilepath);
            const inferredLicense = inferLicense(licenseContent);
            info.licenseText = licenseContent;
            const license = info.license;
            if (typeof license === 'string') {
                if (inferredLicense && isValidLicense(inferredLicense) && !isValidLicense(license)) {
                    // some packages don't specify their license version but we can infer it based on their license file
                    const basicLicense = license.toLowerCase().replace(/(-like|\*)$/g, '');
                    const expandedLicense = inferredLicense.toLowerCase();
                    if (expandedLicense.startsWith(basicLicense)) {
                        // TODO consider doing something to notify the user
                        info.license = inferredLicense;
                    }
                }
            }
            else if (inferredLicense) {
                // if there's no license then infer it based on the license file
                info.license = inferredLicense;
            }
            else {
                // valid expression to refer to a license in a file
                info.license = `SEE LICENSE IN ${licenseFile}`;
            }
        }
    }
    if (typeof info.license === 'string') {
        // sometimes licenses are known by different names, reduce them
        info.license = LICENSE_RENAMES[info.license] || info.license;
    }
    else if (typeof info.readme === 'string') {
        // the license might be at the bottom of the README
        const inferredLicense = inferLicense(info.readme);
        if (inferredLicense) {
            info.license = inferredLicense;
        }
    }
    // get notice file
    const noticeFile = files.find((filename) => {
        const lower = filename.toLowerCase();
        return lower === 'notice' || lower.startsWith('notice.');
    });
    if (noticeFile) {
        const noticeFilepath = path.join(moduleLoc, noticeFile);
        const noticeFileStats = await fs.stat(noticeFilepath);
        if (noticeFileStats.isFile()) {
            info.noticeText = await fs.readFile(noticeFilepath);
        }
    }
    for (const dependencyType of MANIFEST_FIELDS) {
        const dependencyList = info[dependencyType];
        if (dependencyList && typeof dependencyList === 'object') {
            delete dependencyList['//'];
            for (const name in dependencyList) {
                dependencyList[name] = dependencyList[name] || '';
            }
        }
    }
});
